#pragma once
#include <iostream>
#include <vector>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include "oj_view.hpp"
#include <mutex>
#include <bitset>
#include "../compile_server/compile_run.hpp"
#include "../comm/httplib.h"
#include <unistd.h>
// #include "oj_model.hpp"
#include "oj_model_mysql.hpp"

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_view;
    using namespace compile_run;
    using namespace httplib;

    const static std::string machine_conf = "./machine.conf";

    // 主机
    struct Machine
    {
        std::string _ip;
        int _port;
        uint64_t _load;       // 主机负载，每多一个编译运行请求，负载++，否则--
        std::mutex *_mac_mtx; // mutex对象禁止拷贝

        Machine() : _ip(""), _port(0), _load(0), _mac_mtx(nullptr)
        {
        }

        void AddLoad()
        {
            _mac_mtx->lock();
            ++_load;
            //std::cout << _ip << ":" << _port << "主机负载++" << std::endl;
            _mac_mtx->unlock();
        }

        void SubLoad()
        {
            _mac_mtx->lock();
            --_load;
            //std::cout << _ip << ":" << _port << "主机负载--" << std::endl;
            _mac_mtx->unlock();
        }

        void ClearLoad()
        {
            _mac_mtx->lock();
            _load = 0;
            _mac_mtx->unlock();
        }

        uint64_t Load()
        {
            uint64_t load = 0;
            _mac_mtx->lock();
            load = _load;
            _mac_mtx->unlock();
            return load;
        }
    };

    // 负载均衡模块，统筹所有主机
    class LoadBlance
    {
    public:
        LoadBlance()
        {
            assert(LoadAllMachine());
        }
        ~LoadBlance() {}

        /**
         * 加载所有主机
         * 需要有一个配置文件，保存所有主机ip和端口
         */
        bool LoadAllMachine()
        {
            // 读配置文件
            std::ifstream ifs(machine_conf);
            if (!ifs.is_open())
            {
                LOG(FATAL) << "读取主机配置文件失败" << std::endl;
                return false;
            }

            std::string line;
            while (getline(ifs, line))
            {
                std::vector<std::string> sub;

                // 字符串切割
                StringUtil::Split(line, sub, ":");
                if (sub.size() != 2)
                {
                    LOG(ERROR) << "主机 'ip:端口' 格式有误" << std::endl;
                    continue;
                }

                Machine mac;
                mac._ip = sub[0];
                mac._port = atoi(sub[1].c_str());
                mac._load = 0;
                mac._mac_mtx = new std::mutex;
                _online.push_back(_vmac.size()); // 主机id从0开始
                _vmac.push_back(mac); // 移动构造
                // LOG(INFO) << "主机加载成功 [ip: " << sub[0] << ", port: " << mac._port << "]" << std::endl;
            }

            ifs.close();

            return true;
        }

        /**
         * 动态选择使用哪台主机上的cr功能
         * 负载均衡
         * 每次选负载最小的主机
         * 输出型参数，返回主机id和主机对象
         */
        // bool DynamicChoice(int& id, Machine &mac)
        // {
        //     LOG(INFO) << "正在检测负载，选择主机......" << std::endl;
        //     _lb_mtx.lock();

        //     int num = _online.size();
        //     if (num == 0)
        //     {
        //         _lb_mtx.unlock();
        //         LOG(FATAL) << "所有主机均已离线，请检查!!!" << std::endl;
        //         return false;
        //     }

        //     id = _online[0];
        //     mac = _vmac[_online[0]];
        //     uint64_t min_load = _vmac[_online[0]].Load();
        //     for (int i = 1; i < num; ++i)
        //     {
        //         uint64_t cur_load = _vmac[_online[i]].Load();
        //         if (min_load > cur_load)
        //         {
        //             min_load = cur_load;
        //             id = _online[i];
        //             mac = _vmac[id];
        //         }
        //     }

        //     LOG(INFO) << "DynamicChoice()::mac = " << &mac << std::endl;
        //     _lb_mtx.unlock();
        //     return true;
        // }



        //使用引用最后调的是拷贝构造，修改不了_online中主机的负载
        bool DynamicChoice(int* id, Machine **mac)
        {
            //LOG(INFO) << "正在检测负载，选择主机......" << std::endl;
            _lb_mtx.lock();

            int num = _online.size();
            if (num == 0)
            {
                _lb_mtx.unlock();
                LOG(FATAL) << "所有主机均已离线，请检查!!!" << std::endl;
                return false;
            }

            *id = _online[0];
            *mac = &_vmac[_online[0]];
            uint64_t min_load = _vmac[_online[0]].Load();

            for (int i = 1; i < num; ++i)
            {

                uint64_t cur_load = _vmac[_online[i]].Load();
                if (min_load > cur_load)
                {
                    min_load = cur_load;
                    *id = _online[i];
                    *mac = &_vmac[*id];
                }
            }
            
            _lb_mtx.unlock();
            return true;
        }

        // 主机掉线，从可选择主机中移除
        void Offline(int id)
        {
            _lb_mtx.lock();
            auto it = _online.begin();
            while (it != _online.end())
            {
                if (*it == id)
                {
                    _vmac[id].ClearLoad();
                    _online.erase(it);
                    _offline.push_back(id);
                    break;
                }
                ++it; // 少了这行
            }
            _lb_mtx.unlock();
        }

        // 主机上线
        void Online()
        {
            _lb_mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
            _lb_mtx.unlock();

            LOG(INFO) << "所有的主机重新上线" << std::endl;
        }

        // for test
        // 显示所有在线主机和离线主机
        void MachineStatus()
        {
            _lb_mtx.lock();
            std::cout << "在线主机: ";
            for (auto &e : _online)
            {
                std::cout << e << " ";
            }

            std::cout << std::endl;

            std::cout << "离线主机: ";
            for (auto &e : _offline)
            {
                std::cout << e << " ";
            }

            _lb_mtx.unlock();
            std::cout << std::endl;
        }

    private:
        std::vector<Machine> _vmac; // 记录所有主机，以下标作为主机标识（id）

        std::vector<int> _online; // 记录在线主机(id)

        std::vector<int> _offline; // 记录所有离线主机(id)

        std::mutex _lb_mtx;
    };

    // 联系网页和数据模块,核心业务逻辑
    class Controller
    {
    public:
        Controller()
        {
        }
        ~Controller() {}

        /**
         * 将所以题目数据构建成网页并返回
         * 输出型参数
         */

        void Recovery()
        {
            _load.Online();
        }

        bool AllQuestion(std::string &html)
        {
            bool ret = true;

            std::vector<struct Question> ques;
            if (_model.GetAllQuestion(ques))
            {
                // //排序,让题目安装编号升序显示
                // std::sort(ques.begin(), ques.end(), [](const struct Question* q1, const struct Question* q2)
                // {
                //     return atoi(q1->number.c_str()) < atoi(q2->number.c_str());
                // });

                // 获取题目信息成功，构建网页
                //LOG(INFO) << "获取题目信息成功，构建网页..." << std::endl;
                ret = _view.AllExpandHtml(ques, html);
            }

            else
            {
                LOG(FATAL) << "获取题目列表失败" << std::endl;
                html = "获取题目列表失败";
                ret = false;
            }

            if(ret)
            {
                LOG(INFO) << "题库加载成功" << std::endl;
            }
            return ret;
        }

        bool Question(const std::string &number, std::string &html)
        {
            bool ret = true;

            struct Question ques;
            if (_model.GetOneQuestion(number, ques))
            {
                _view.OneExpandHtml(ques, html);
            }
            else
            {
                html = "题目: " + number + "不存在";
                ret = false;
            }

            return ret;
        }

        /**
         * in_json:
         * "code":
         * "input":
         * "cpu_limit":
         * "mem_limit":
         *
         * */

        void Judge(std::string number, std::string in_json, std::string &up_out_json)
        {
            // 根据题号找到题目
            struct Question ques_;
            _model.GetOneQuestion(number, ques_);
            // 反序列化in_json，提取code
            Json::Value out_val;
            Json::Reader read_;
            read_.parse(in_json, out_val);
            std::string cr_code = out_val["code"].asString();
            // 重新拼接：code + 测试用例代码 = 编译运行代码(cr_code)
            cr_code += '\n'; // 这里最好自带一个换行，否则拼接后可能出现宏黏着在上一行的现象
            cr_code += ques_.tail;
            // 序列化cr_code
            Json::Value cr_val;
            cr_val["code"] = cr_code;
            cr_val["input"] = "";
            cr_val["cpu_limit"] = ques_.cpu_limit;
            cr_val["mem_limit"] = ques_.mem_limit;
            Json::FastWriter wr_;
            std::string cr_json = wr_.write(cr_val);

            /**
             * 选择负载最低主机执行cr
             * 选择方式，一直选择，直到cr成功。否则全部挂掉
             */

            // while (true)
            // {
            //     LOG(INFO) << "开始选择主机" << std::endl;

            //     int id_= 0;
            //     Machine mac_;

            //     if (!_load.DynamicChoice(id_, mac_))
            //     {
            //         // 没有可用主机
            //         LOG(FATAL) << "没有可用主机，正在重新上线" << std::endl;
            //         Recovery();
            //         break;
            //     }

            //     /**
            //      * 选到可用主机了,主机负载++
            //      * 以client的身份申请执行cr
            //      * 若cr端响应失败，则cr主机可能离线了
            //      */
            //     LOG(INFO) << "选择主机成功, ip: " << mac_._ip << ", port: " << mac_._port << " 当前主机负载: " << mac_.Load() << std::endl; 
            //     mac_.AddLoad();

            //     Client cr_client(mac_._ip, mac_._port);

            //     /**
            //      * Client::Post
            //      * 参数1：请求资源路由
            //      * 参数2：传递过去的参数
            //      * 参数3：参数类型
            //      */
                
            //     /**
            //      * 设置超时，否则如果用户代码中含while(1)死循环
            //      * 可能是无意，也可能是恶意攻击
            //      * 都会导致服务器宕机：认为服务主机全部离线，但实际并没有。
            //      * 只是因为while(1)，服务主机运行了一段时间后才检测到时间超限然后才开始响应，
            //      * 但是client超时时间太短，所以认为服务主机没有响应。
            //      * **/
            //     cr_client.set_read_timeout(100,0);

            //     if (auto resp = cr_client.Post("/compile_run", cr_json, "application/json;utf-8"))
            //     {
            //         if (resp->status == 200)
            //         {
            //             up_out_json = resp->body;
            //             mac_.SubLoad(); // 完成请求后，主机负载--
            //             LOG(INFO) << "请求编译运行服务结束" << std::endl;
            //             break;
            //         }

            //         LOG(ERROR) << "请求成功，但网页状态码错误: " << resp->status <<std::endl;
            //         mac_.SubLoad(); // 请求成功，但网页状态码不正确
            //     }

            //     else
            //     {
            //         LOG(ERROR) << "当前主机可能已经离线, ip: " << mac_._ip << "port: " << mac_._port << std::endl;
            //         _load.Offline(id_);
            //         _load.MachineStatus();
            //     }
            // }


            while (true)
            {
                //LOG(INFO) << "开始选择主机" << std::endl;

                int id_;
                Machine* mac_ = nullptr;

                if (!_load.DynamicChoice(&id_, &mac_))
                {
                    // 没有可用主机
                    //LOG(FATAL) << "没有可用主机，正在重新上线" << std::endl;
                    //Recovery); //重新上线所有主机
                    break;
                }

                /**
                 * 选到可用主机了,主机负载++
                 * 以client的身份申请执行cr
                 * 若cr端响应失败，则cr主机可能离线了
                 */
                LOG(INFO) << "选择主机成功, ip: " << mac_->_ip << ", port: " << mac_->_port << " 当前主机负载: " << mac_->Load() << std::endl; 
                mac_->AddLoad();

                Client cr_client(mac_->_ip, mac_->_port);

                /**
                 * Client::Post
                 * 参数1：请求资源路由
                 * 参数2：传递过去的参数
                 * 参数3：参数类型
                 */
                
                /**
                 * 设置超时，否则如果用户代码中含while(1)死循环
                 * 可能是无意，也可能是恶意攻击
                 * 都会导致服务器宕机：认为服务主机全部离线，但实际并没有。
                 * 只是因为while(1)，服务主机运行了一段时间后才检测到时间超限然后才开始响应，
                 * 但是client超时时间太短，所以认为服务主机没有响应。
                 * **/
                cr_client.set_read_timeout(20,0);

                if (auto resp = cr_client.Post("/compile_run", cr_json, "application/json;utf-8"))
                {
                    if (resp->status == 200)
                    {
                        up_out_json = resp->body;
                        mac_->SubLoad(); // 完成请求后，主机负载--
                        LOG(INFO) << "请求编译运行服务结束" << std::endl;
                        break;
                    }

                    LOG(ERROR) << "请求成功，但网页状态码错误: " << resp->status <<std::endl;
                    mac_->SubLoad(); // 请求成功，但网页状态码不正确
                }

                else
                {
                    LOG(ERROR) << "当前主机可能已经离线, ip: " << mac_->_ip << "port: " << mac_->_port << std::endl;
                    _load.Offline(id_);
                    _load.MachineStatus();
                }
            }
        }

    private:
        Model _model;
        View _view;
        LoadBlance _load;
    };
}
